import sys
import time
import signal
import psutil
import os
import multiprocessing as mp

from cc.appcoin2.support.py_module.py_driver import (
    PyDriver,
    PyLogger,
    PyStratForwarder)
from cc.appcoin2.support.symbology.py_symbology import PyProduct


class GracefulKiller:
  def __init__(self, parent_pid):
    signal.signal(signal.SIGTERM, self.exit_gracefully)
    signal.signal(signal.SIGINT, self.exit_gracefully)
    self._parent_pid = parent_pid

  def exit_gracefully(self, *args):
    if not self._parent_pid:
      return
    parent = psutil.Process(self._parent_pid)
    for child in parent.children(recursive=True):
      print("kill {}".format(child.pid))
      child.terminate()
    print("kill {}".format(parent.pid))
    parent.kill()


class StrategyBase:
  def __init__(self, driver_config):
    self._driver_config = driver_config
    self._driver = None
    self._strat = None

    self._cpp_log_file_path = None
    self._redirect_py_print = False
    self._py_log_file_path = None

    self._og_name_list = []

    self._strat_pipe_outgoing = None
    self._strat_pipe_incoming = None
    self._drive_pipe_outgoing = None
    self._drive_pipe_incoming = None

    self._sub_process = None

    self._enable_telemetry = False

  def set_pipe(self, pipe0, pipe1):
    self._strat_pipe_outgoing = pipe0[0]
    self._strat_pipe_incoming = pipe1[0]
    self._drive_pipe_outgoing = pipe1[1]
    self._drive_pipe_incoming = pipe0[1]

  def get_default_log_path(self, prefix):
    if "strategy" not in self._driver or "strategy_name" not in self._driver["strategy"]:
      return None  # just log to stdout/stderr
    return "{}/log/{}_{}_log".format(os.environ['HOME'],
                                     self._driver["strategy"]["strategy_name"],
                                     prefix)

  def redirect_cpp_log_to_file(self, log_path, redirect_py_print: bool):
    if not log_path:
      log_path = self.get_default_log_path("cpp")
    if not log_path:
      return
    # TODO: check file path
    self._cpp_log_file_path = log_path
    self._redirect_py_print = redirect_py_print

  def redirect_py_log_to_file(self, log_path, redirect_py_print: bool):
    if not log_path:
      log_path = self.get_default_log_path("py")
    if not log_path:
      return
    # TODO: check file path
    self._py_log_file_path = log_path
    self._redirect_py_print = redirect_py_print

  def get_all_working_orders(self, me='ALL'):
    working_orders = []
    for os_name in self._og_name_list:
      inner_working_orders = self._strat.get_all_working_orders(os_name)
      for order in inner_working_orders:
        order_dict = order.as_dict()
        inner_me = "{}:{}".format(order_dict['market'], order_dict['exchange'])
        if me != 'ALL' and me != inner_me:
          break
        working_orders.append(order.as_dict())
    return working_orders

  # outgoing
  def submit_order(self, exec_spec: dict):
    if 'og_name' not in exec_spec:
      return
    if 'order_spec' not in exec_spec:
      return
    og_name = exec_spec['og_name']
    if og_name not in self._og_name_list:
      return
    exec_spec['exec_type'] = 'SUBMIT_ORDER'
    self._strat.submit_execution(exec_spec)

  def cancel_order(self, exec_spec: dict):
    if 'og_name' not in exec_spec:
      return
    if 'proc_order_id' not in exec_spec:
      return
    og_name = exec_spec['og_name']
    if og_name not in self._og_name_list:
      return
    exec_spec['exec_type'] = 'CANCEL_ORDER'
    self._strat.submit_execution(exec_spec)

  def on_poll_fail(self):
    if self._sub_process.is_alive():  # TODO(daniel): any better solution?
      return
    import traceback
    print(traceback.format_exc())
    raise "exit due to driver process dead"

  # run in py strategy thread
  def run_strat(self):
    print("parent process: {}".format(mp.current_process().pid))
    # redirect log
    if self._py_log_file_path:
      print("parent process: redirect log to file: {}".format(self._py_log_file_path))
      PyDriver.redirect_cpp_log(self._py_log_file_path, 2)
      sys.stdout = PyLogger()
    # start strategy
    try:
      self._strat = PyStratForwarder(self, self._strat_pipe_incoming, self._strat_pipe_outgoing)
      self._strat.run()
    except Exception as exc:
      import traceback
      print(traceback.format_exc())
      while self._sub_process.is_alive():
        self._sub_process.terminate()
        time.sleep(1)
      raise

  # run in cpp driver thread
  def run_async(self):
    print("child  process: {}".format(mp.current_process().pid))
    # redirect log
    if self._cpp_log_file_path:
      print("child  process: redirect log to file: {}".format(self._cpp_log_file_path))
      PyDriver.redirect_cpp_log(self._cpp_log_file_path, 2)
      sys.stdout = PyLogger()
    # start driver
    self._driver = PyDriver(self._driver_config,
                            self._drive_pipe_incoming,
                            self._drive_pipe_outgoing)
    self._driver.run()

  def run(self):
    process_killer = GracefulKiller(mp.current_process().pid)
    pipe1 = mp.Pipe()
    pipe2 = mp.Pipe()
    self.set_pipe(pipe1, pipe2)

    def run_drive(strategy):
      strategy.run_async()

    self._sub_process = mp.Process(target=run_drive, args=(self,))
    self._sub_process.start()

    self.run_strat()
